﻿using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;

namespace RemoteObject
{

    [Serializable]
    public class BlockInfo
    {
        public int blockID, originPos, currentPos, remainDataLength;

    }

    [Serializable]
    public class Downloader
    {
        private String fnRemote;
        private String fnLocal;
        private int fileLength;
        private float downloadBytesPerSec = 0;
        private float downloadSpeed = 0;
        private float progress;
        private int startPos;
        private int endPos;
        private Boolean stopFlag = false;

        //public Thread MyThread { get; set; }
        public bool ThreadFinished { get; set; }

        FileStream fs;
        TransferParam param = new TransferParam();
        List<Byte[]> blocksReceived = new List<Byte[]>();

        BlockInfo blockInfo;
        DownloadJob ownerJob;

        //debug use only
        double fileWriteTime = 0;
        double totalDownloadTime = 0;
        //debug use only -end

        public Downloader(String remoteFile, String localFile, int fileSize, BlockInfo _blockInfo, DownloadJob _job)
        {
            fnRemote = remoteFile;
            fnLocal = localFile;
            fileLength = fileSize;
            blockInfo = _blockInfo;
            ownerJob = _job;
            ownerJob.downloaderList.Add(this);

            float completedLength = blockInfo.currentPos - blockInfo.originPos;
            float totalLength = blockInfo.remainDataLength + completedLength;
            progress = completedLength / totalLength * 100;
        }

        private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            downloadSpeed = downloadBytesPerSec;
            downloadBytesPerSec = 0;
            //Console.WriteLine("TimerElapsed::Block [" + blockInfo.blockID + "] --- " + (downloadSpeed / 1024).ToString("f1") + " KB/S");
        }

        private void UpdateProgress()
        {
            ownerJob.UpdateProgressFile();

            float completedLength = blockInfo.currentPos - blockInfo.originPos;
            float totalLength = blockInfo.remainDataLength + completedLength;
            progress = completedLength / totalLength * 100;

            //PrintProgress(blockInfo);
        }

        private void WriteFileProc()
        {
            do
            {
                Thread.Sleep(10);

                List<Byte[]> blocksReceivedToSave;
                lock (this)
                {
                    blocksReceivedToSave = blocksReceived;
                    blocksReceived = new List<Byte[]>();
                }

                if (blocksReceivedToSave.Count != 0)
                {
                    foreach (Byte[] block in blocksReceivedToSave)
                    {
                        if (block == Common.endingBlock)
                        {
                            UpdateProgress();
                            if (fs != null)
                                fs.Close();
                            blocksReceivedToSave.Clear();
                            ThreadFinished = true;
                            ownerJob.DownloaderFinish(this);
                            System.GC.Collect();
                            return;
                        }
                        else
                        {
                            Byte[] blockToSave = block;
                            if (param.useCompressMode)
                            {
                                blockToSave = RemoteObject.MyUtility.DeCompression(block);
                            }
                            fs.Write(blockToSave, 0, blockToSave.Length);

                            //after save successful, then update the block info. This is important for broken-resuming
                            blockInfo.currentPos += blockToSave.Length;
                            blockInfo.remainDataLength -= blockToSave.Length;
                        }
                    }
                    UpdateProgress();
                    blocksReceivedToSave.Clear();
                }

            } while (true);
        }

        public void Run()
        {
            DateTime startTime = DateTime.Now;

            stopFlag = false;

            startPos = blockInfo.currentPos;
            endPos = blockInfo.currentPos + blockInfo.remainDataLength;
            int dataLength = blockInfo.remainDataLength;

            //set transfer param
            param.BlockSize = Common.BlockSize;
            param.BuffSize = Common.BlockSize * 5;
            param.useCompressMode = false;

            try
            {
                Console.WriteLine(startPos + " --- " + (startPos + dataLength));

                // create the write file thread anyway
                Thread thread = new Thread(new ThreadStart(this.WriteFileProc));
                thread.Start();

                RemoteObject.MyRemoteObject proxyObjectTcp = null;
                long clientID = 0;

                //try to init file transfer
                try
                {
                    proxyObjectTcp = (RemoteObject.MyRemoteObject)Activator.GetObject(typeof(RemoteObject.MyRemoteObject), ownerJob.GetServerURL());
                    clientID = proxyObjectTcp.GetNewClientID();
                    proxyObjectTcp.SetTransferParam(clientID, param);
                    proxyObjectTcp.StartDownloadFile(fnRemote, clientID, startPos, dataLength);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Network problem ..... ");
                    Console.WriteLine(e);
                    proxyObjectTcp = null;
                }
                finally
                {
                }

                if (proxyObjectTcp != null)
                {
                    System.Timers.Timer SpeedTimer = new System.Timers.Timer();
                    SpeedTimer.Enabled = true;
                    SpeedTimer.Interval = 1000;
                    SpeedTimer.Elapsed += new System.Timers.ElapsedEventHandler(TimerElapsed);

                    try
                    {
                        fs = new FileStream(fnLocal, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                        fs.Seek(startPos, SeekOrigin.Begin);

                        while (true)
                        {
                            Byte[] receiveData = proxyObjectTcp.RecevingFileData(clientID);
                            if (receiveData == null || receiveData.Length == 0 || stopFlag)
                                break;
                            lock (this)
                            {
                                blocksReceived.Add(receiveData);
                            }
                            downloadBytesPerSec += receiveData.Length;
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(startPos + " --- " + (startPos + dataLength) + " ---- error1 --- ");
                        Console.WriteLine(e);
                    }
                    finally
                    {
                        SpeedTimer.Close();
                        downloadSpeed = 0;
                        proxyObjectTcp.StopDownloadFile(clientID);
                    }
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(startPos + " --- " + (startPos + dataLength) + " ---- error2 --- ");
                Console.WriteLine(e);
            }
            finally
            {
                //we should always give the endingBlock to let write-file thread close gracefully.
                lock (this)
                {
                    blocksReceived.Add(Common.endingBlock);
                }

                //debug code
                TimeSpan ts = DateTime.Now - startTime;
                totalDownloadTime += ts.TotalMilliseconds;
                //debug code - end

                Console.WriteLine(startPos + " --- " + (startPos + dataLength) + " ---- done --- " + fileWriteTime + " ---- duration --- " + totalDownloadTime);

            }
            System.GC.Collect();
        }

        public void StopDownload()
        {
            stopFlag = true;
        }

        public float GetProgress()
        {
            //for debug only
            //Console.WriteLine("Block [" + blockInfo.blockID + "] --- " + progress.ToString("f1") + " %");
            //for debug only
            return progress;
        }

        public float GetDownloadSpeed()
        {
            //for debug only
            //Console.WriteLine("Block [" + blockInfo.blockID + "] --- " + (downloadSpeed / 1024.0f).ToString("f1") + " KB/s");
            //for debug only
            return downloadSpeed;
        }

        public int GetStartPos()
        {
            return startPos;
        }

        public int GetEndPos()
        {
            return endPos;
        }
    }
}
